package com.mainbo.androidframework.net.Retrofit;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;


import com.mainbo.androidframework.net.FailedType;
import com.mainbo.androidframework.net.NetCallBack;
import com.mainbo.androidframework.net.Retrofit.RetrofitCall;
import com.mainbo.androidframework.utils.LogUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import retrofit.Call;
import retrofit.CallAdapter;
import retrofit.Callback;
import retrofit.Response;
import retrofit.Retrofit;


/**
 * Created by wei-pc on 2015/11/26.
 */
public class CustomCallAdapter {
    private final static String TAG = "NET";
    private static final int MESSAGE_SUCCESS = 0x11;
    private static final int MESSAGE_ERROR = 0x12;
    private static Handler mHandler = new InternalHandler();

    public static class CustomCallAdapterFactory implements CallAdapter.Factory {

        @Override
        public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

            if (!(returnType instanceof ParameterizedType)) {
                throw new IllegalStateException(
                        "RetrofitCall must have generic type (e.g., RetrofitCall<ResponseBody>)");
            }
            final Type responseType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
            return new CallAdapter<RetrofitCall<?>>() {

                @Override
                public Type responseType() {
                    return responseType;
                }

                @Override
                public <R> RetrofitCall<?> adapt(Call<R> call) {
                    return new RetrofitCallAdapter<>(call);
                }
            };
        }
    }

    static class RetrofitCallAdapter<T> implements RetrofitCall<T> {
        private final Call<T> call;

        RetrofitCallAdapter(Call<T> call) {
            this.call = call;
        }

        @Override
        public void cancel() {
            this.call.cancel();
        }

        @Override
        public void enqueue(final NetCallBack<T> callback) {
            call.enqueue(new Callback<T>() {
                @Override
                public void onResponse(Response<T> response, Retrofit retrofit) {
                    int code = response.code();
                    FailedType failedType;
                    LogUtil.i(TAG, "Callback Thread : " + Thread.currentThread().getName());
                    if (code >= 200 && code < 300) {
                        sendSuccessMsg(callback, response.body());
                        return;
                    }
                    if (code == 401) {
                        failedType = FailedType.UNAUTHENTICATED;
                    } else if (code >= 400 && code < 500) {
                        failedType = FailedType.CLIENT_ERROR;
                    } else if (code >= 500 && code < 600) {
                        failedType = FailedType.SERVER_ERROR;
                    } else {
                        failedType = FailedType.UNEXPECTED_ERROR;
                    }
                    //todo: set fail message
                    sendErrorMsg(callback, code, failedType, "");
                }

                @Override
                public void onFailure(Throwable t) {
                    t.printStackTrace();
                    sendErrorMsg(callback, 0, FailedType.UNEXPECTED_ERROR, FailedType.UNEXPECTED_ERROR.defaultMsg);
                }
            });
        }

        @Override
        public Response<T> execute() throws Exception{
        return call.execute();
        }

        private void sendSuccessMsg(NetCallBack<T> callback, T t) {
            LogUtil.i(TAG, "success");
            CallResult<T> callResult = new CallResult<>(callback);
            callResult.setSuccessData(t);
            Message message = new Message();
            message.what = MESSAGE_SUCCESS;
            message.obj = callResult;
            mHandler.sendMessage(message);
        }

        private void sendErrorMsg(NetCallBack<T> callback, int errorCode, FailedType failedType, String errorStr) {
            LogUtil.w(TAG, "errorCode : " + errorCode + " errorStr : " + errorStr);
            CallResult<T> callResult = new CallResult<>(callback);
            callResult.setErrorData(errorCode, failedType, errorStr);
            Message message = new Message();
            message.what = MESSAGE_ERROR;
            message.obj = callResult;
            mHandler.sendMessage(message);
        }

        @Override
        public RetrofitCall<T> clone() {
            return new RetrofitCallAdapter<>(call.clone());
        }
    }

    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            CallResult result = (CallResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_SUCCESS:
                    result.callback.success(result.t);
                    break;
                case MESSAGE_ERROR:
                    result.callback.failed(result.errorCode, result.failedType, result.errorStr);
                    break;
            }
        }
    }

    private static class CallResult<T> {
        NetCallBack<T> callback;
        T t;
        int errorCode;
        FailedType failedType;
        String errorStr;

        CallResult(NetCallBack<T> callback) {
            this.callback = callback;
        }

        void setSuccessData(T t) {
            this.t = t;
        }

        void setErrorData(int code, FailedType failedType, String errorStr) {
            this.errorCode = code;
            this.failedType = failedType;
            this.errorStr = errorStr;
        }
    }
}
